using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;

namespace LightCAD.Three
{
    public class LODLevel
    {
        public Object3D _object;
        public double distance;
        public double hysteresis;
    }

    public class LOD : Object3D
    {
        #region scope properties or methods
        //private static Vector3 _v1 = new Vector3();
        //private static Vector3 _v2 = new Vector3();
        private LODContext getContext() =>
            ThreeThreadContext.GetCurrThreadContext().LODCtx;
        #endregion

        #region Properties

        public int _currentLevel;
        public bool autoUpdate;
        private ListEx<LODLevel> levels;
        #endregion

        #region constructor
        public LOD()
        {
            this._currentLevel = 0;
            this.type = "LOD";
            this.levels = new ListEx<LODLevel>();
            this.autoUpdate = true;
        }
        #endregion

        #region methods
        public LOD copy(LOD source)
        {
            base.copy(source, false);
            var levels = source.levels;
            for (int i = 0, l = levels.Count; i < l; i++)
            {
                var level = levels[i];
                this.addLevel(level._object.clone(), level.distance, level.hysteresis);
            }
            this.autoUpdate = source.autoUpdate;
            return this;
        }
        public override Object3D copy(Object3D source, bool recursive = true)
        {
            return copy(source as LOD, recursive);
        }
        public override Object3D clone(bool recursive = true)
        {
            return new LOD().copy(this, recursive);
        }
        public LOD addLevel(Object3D _object, double distance = 0, double hysteresis = 0)
        {
            distance = Math.Abs(distance);
            var levels = this.levels;
            int l;
            for (l = 0; l < levels.Count; l++)
            {
                if (distance < levels[l].distance)
                {
                    break;
                }
            }
            levels.Splice(l, 0, new LODLevel() { distance = distance, hysteresis = hysteresis, _object = _object });
            this.add(_object);
            return this;
        }
        public double getCurrentLevel()
        {
            return this._currentLevel;
        }
        public Object3D getObjectForDistance(double distance)
        {
            var levels = this.levels;
            if (levels.Count > 0)
            {
                int i, l;
                for (i = 1, l = levels.Count; i < l; i++)
                {
                    var levelDistance = levels[i].distance;
                    if (levels[i]._object.visible)
                    {
                        levelDistance -= levelDistance * levels[i].hysteresis;
                    }
                    if (distance < levelDistance)
                    {
                        break;
                    }
                }
                return levels[i - 1]._object;
            }
            return null;
        }
        public override void raycast(Raycaster raycaster, ListEx<Raycaster.Intersection> intersects = null, object vars = null)
        {
            var levels = this.levels;
            if (levels.Count > 0)
            {
                var _v1 = getContext()._v1;
                _v1.SetFromMatrixPosition(this.matrixWorld);
                var distance = raycaster.ray.Origin.DistanceTo(_v1);
                this.getObjectForDistance(distance).raycast(raycaster, intersects);
            }
        }
        public void update(Camera camera)
        {
            var levels = this.levels;
            if (levels.Count > 1)
            {
                var _v1 = getContext()._v1;
                var _v2 = getContext()._v2;
                _v1.SetFromMatrixPosition(camera.matrixWorld);
                _v2.SetFromMatrixPosition(this.matrixWorld);
                var distance = _v1.DistanceTo(_v2) / camera.zoom;
                levels[0]._object.visible = true;
                int i, l;
                for (i = 1, l = levels.Count; i < l; i++)
                {
                    var levelDistance = levels[i].distance;
                    if (levels[i]._object.visible)
                    {
                        levelDistance -= levelDistance * levels[i].hysteresis;
                    }
                    if (distance >= levelDistance)
                    {
                        levels[i - 1]._object.visible = false;
                        levels[i]._object.visible = true;
                    }
                    else
                    {
                        break;
                    }
                }
                this._currentLevel = i - 1;
                for (; i < l; i++)
                {
                    levels[i]._object.visible = false;
                }
            }
        }
        #endregion

    }
}
